// scaup - Scala up!
// (c) 2008-2009, Normen Müller, normen.mueller@gmail.com  
// $Id: GM.scala 2482 2009-10-07 16:55:33Z nmueller $
package locutor.model.parsing.grgen

import java.io.File
import java.text.MessageFormat.{format}

import scaup.Views._

/** This object provides a simple interface to the GM parser class.
 *
 * @thanks GrGEN.NET manual and GrGen.g#TYPES for syntax rules (v2.5.x)
 * 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.5 */
object GM extends GMParser {
  def apply(input: String): T = apply("locutor", input)
  
  def apply(input: File): T =
    if(input == null || !(input exists))
      throw new Exception(format("Physical GM file may not be null (''{0}'').", input))
    else apply(input.name, input.content)
  
  def apply(name: String, input: String) =
    if(input == null)
      throw new Exception("Physical GM representation may not be null.")
    else parse(name, input)
  
  def parse(name: String, input: String) = phrase(gm(name))(new lexical.Scanner(input)) match {
    case Success(result, _) => result
    case n@_                => throw new Exception(format("GM parsing error in ''{0} ...}''\n", input.lines.next) + n)
  }
}

/** A GrGEN graph model (.gm) parser.
 * 
 * At the moment only primitive string representations of GrGEN graph models are supported.
 * No need for sophisticated AST yet due to direct looping into GrGEN.
 * 
 * Restrictions:
 * - Can't parse, for example, ``const operatorMap:map<dom_AssignmentOperatorKind, string> = { ... }''
 * 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.5 */
class GMParser extends ExprParser {
  /* Fill in abstract defs*/
  type Tokens = GMLexer
  val lexical: Tokens = new GMLexer
  
  /* Configure lexical parsing */                                 
  lexical.reserved ++= List(
    "abstract", "arbitrary", 
    "boolean",
    "const", "class", "connect",
    "directed",
    "enum", "edge", "extends",
    "inherited",
    "object",
    "node",   
    "undirected", 
    "set"     
    // Expression parser reserved:
    // "int", "double", "float", "string",
    // "true", "false", "null",
    // "visited", "nameof", "typeof"
  ) ++ ExprParser.reserved
  
  lexical.delimiters ++= List(
    "{", "}",
    "->", "--", "<->",
    ";", "="
    // Expression parser delimiters:
    // "[", "]", "(", ")",
    // "!", "?", ":", ",", ".", "::", 
    // "^", "&&", "||", "&", "|",
    // "<<", ">>>", ">>", "+", "-", "*", "/", "%", "~",
    // "<=", "<", "==", "!=", ">=", ">"
  ) ++ ExprParser.delimiters
  
  /* Define the grammar */
  def gm(name: String) = (enums*) ~ (classes*) ^^ {
    case enums ~ classes => T(name, enums ::: classes)
  }

  def enums = ("enum" ~> annotated_ident) ~ ("{" ~> repsep((annotated_ident ~ opt("=" ~> expr)), ",")) <~ "}" ^^ {
    case id ~ is => "enum " + id + ((is map {case i ~ Some(v) => i+"="+v case i ~ None => i}) mkString (" {", ",", "}"))
  }                                                
  
  def classes = opt("abstract") ~ opt("const") ~ (node | edge) ^^ {
    case abst ~ cons ~ clazz =>
      (abst match {case Some(_) => "abstract " case _ => ""}) + 
      (cons match {case Some(_) => "const " case _ => ""}) +
      clazz
  }
  
  def node =
    ( ("node" ~ "class") ~> (annotated_ident ~ opt(extends_stmt)) <~ ";" ^^ {
      case id ~ ex => "node class " + id + ∑(ex) + ";"
    }
    | ("node" ~ "class") ~> (annotated_ident ~ opt(extends_stmt)) ~ ("{" ~> ((attr)*) <~ "}") ^^ {
      case id ~ ex ~ as => "node class " + id + ∑(ex) + (as mkString (" {", "", "}"))
    }
    )
  
  def edge = 
    ( opt(direction) ~ (("edge" ~ "class") ~> annotated_ident) ~ opt(extends_stmt) ~ (opt(connect) <~ ";") ^^ {
      case dir ~ id ~ ex ~ cs => "edge class " + ∑(dir) + id + ∑(ex) + ∑(cs) + ";"
    }
    | opt(direction) ~ (("edge" ~ "class") ~> annotated_ident) ~ opt(extends_stmt) ~ opt(connect) ~ ("{" ~> ((attr)*) <~ "}") ^^ {
      case dir ~ id ~ ex ~ cs ~ as =>"edge class " + ∑(dir) + id + ∑(ex) + ∑(cs) + (as mkString (" {", "", "}"))
    }
    )

  def direction = "arbitrary" | "undirected" | "directed"

  def connect = "connect" ~> (assertion ~ (("," ~> assertion)*)) ^^ {
    case c ~ cs => " connect "+((c :: cs) mkString ",")
  }

  def assertion =
    ( "inherited"
    | ident ~ opt(range) ~ ("->" | "<->" | "--") ~ ident ~ opt(range) ^^ {
      case dom ~ domr ~ d ~ cod ~ codr => dom + ∑(domr) + d + cod + ∑(codr)  
    }
    )
  
  def extends_stmt = "extends" ~> (ident ~ (("," ~> ident)*)) ^^ {
    case i ~ is => " extends "+((i :: is) mkString ",")
  }

  def attr = 
    ( annotated_ident ~ (":" ~> types) ~ opt("=" ~> expr) <~ ";" ^^ {
      case id ~ t ~ e => id + ":" + t + (e match {case Some(x) => "="+x case _ => ""}) + ";"
    }
    | annotated_ident ~ ("=" ~> expr <~ opt(";")) ^^ {
      case id ~ e => id + "=" + e + ";"
    }
    )
  
  def types = ident | primitives | ("set" ~ "<") ~> primitives <~ ">" ^^ {case p => "set<"+p+">"}

}

import scala.util.parsing.combinator._
import scala.util.parsing.combinator.lexical._
import scala.util.parsing.input.CharArrayReader.EofCh

/** 
 * @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 * @version 0.0.5 */
private [grgen] class GMLexer extends StdLexical with RegexParsers {
  override type Elem = Char
  
  override def token = 
    ( string ^^ StringLit
    | number ^^ NumericLit
    | delim
    | id     ^^ {case i if reserved contains i => Keyword(i) case i => Identifier(i)}  
    | EofCh ^^^ EOF    
    | '\"' ~> failure("Unterminated string")
    | failure("Illegal character")
    )
  
  def string  = '\"' ~> rep(charSeq | chrExcept('\"', '\n', EofCh)) <~ '\"' ^^ { _ mkString "" }  
  
  def number  = """-?(\d+(\.\d*)?|\d*\.\d+)([eE][+-]?\d+)?[fFdD]?""".r
  
  def id      = rep(letter | digit | elem("underscore", _=='_')) ^^ { _ mkString "" }
  
  def charSeq = 
    ( '\\' ~ '\"' ^^^ "\"" | '\\' ~ '\\' ^^^ "\\" | '\\' ~ '/'  ^^^ "/" | '\\' ~ 'b'  ^^^ "\b" | '\\' ~ 'f'  ^^^ "\f" 
    | '\\' ~ 'n'  ^^^ "\n" |'\\' ~ 'r'  ^^^ "\r" |'\\' ~ 't'  ^^^ "\t" |'\\' ~ 'u' ~> unicodeBlock
    )
  
  import scala.io.UTF8Codec.{encode}
  def unicodeBlock = hexDigit ~ hexDigit ~ hexDigit ~ hexDigit ^^ {
    case a ~ b ~ c ~ d => new String(encode(Integer.parseInt(List(a, b, c, d) mkString "", 16)))
  }
  
  val hexDigits = Set[Char]() ++ "0123456789abcdefABCDEF".toArray
  
  def hexDigit = elem("hex digit", hexDigits.contains(_))
}
